﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using TrainGame.Model;
using TrainGame.Engine;

namespace TrainGame.AI.TileLay
{
	/// <summary>
	/// Improve routes for current trains
	/// </summary>
	internal class ImproveRoutesForPlannedTrainsTileGoal : TileLayGoal
	{
		public override string GetName(PublicCompany company)
		{
			return "ImproveRoutesForPlannedTrains";
		}

		/// <summary>
		/// These are cached by ComputeNeed and used by ComputeTileLays
		/// </summary>
		private TrainCollection _nextTurnTrains;
		private bool _needIsDesperate;

		/// <summary>
		/// how much do we need to accomodate next turn's trains?
		/// </summary>
		protected override int ComputeNeed(Gameboard gameboard, PublicCompany company, Dictionary<string, GameboardHex> runToHexes, AiMoveConfig config)
		{
			// note: cached for later use by ComputeMove
			_nextTurnTrains = SimulateTrainPurchaseAi(company, company.Trains, company.Game);

			// treat the need as 1/2 since we're looking at next turn
			List<string> tooShortEngpoints = new List<string>();
			int need = ImproveCurrentRoutesTileGoal.CalculateNeedForTrains(gameboard, company, _nextTurnTrains, company.Game.Tiles,
				config, tooShortEngpoints, out _needIsDesperate);
			return need / 2;
		}

		static private TrainCollection SimulateTrainPurchaseAi(PublicCompany company, TrainCollection companyTrains, Game game)
		{
			// TODO: guts of this method should eventually move to a train purchase AI class

			// start with current trains
			TrainCollection trains = new TrainCollection(companyTrains);
			// get next few trains for purchase
			TrainCollection nextTrains = GetNextTrains(game, 6);	// we don't expect to ever buy more than 6! (3 2's from other companies, then the first 4, then 2 others from wherever)

			// keep track of these in temp variables, so we can modify them as we simulate buying trains
			int cash = company.Cash;
			// TODO: Loan AI could contribute expected loans
			int trainLimit = game.TrainLimit;

			// add expected train purchases (this turn) - and remove any that rust because of them
			for (int t=0; (t < nextTrains.Count) && (trains.Count < trainLimit); t++)
			{
				Train next = nextTrains[t];

				// TEMP - don't have more than 2 of any type (ideally should allow 3 2's if can also buy a 3, IN SOME CIRCUMSTANCES)
				if (CountTrainSize(trains, next.Stops) >= 2)
					break;	// all done

				// TEMP: real dumb - buy it if we have the cash, or if we don't have any trains
				if ((cash < next.Cost) && (trains.Count > 0))
					break;

				trains.Add (next);
				cash -= next.Cost;
			}

			// TODO: remove expected train losses (anything about to rust?  And from someone else's purchase?)

			return trains;
		}

		// this might eventually move to train-purchase AI
		static private TrainCollection GetNextTrains(Game game, int max)
		{
			TrainCollection nextTrains = new TrainCollection();
			int count = 0;

			// for now just look at new trains
			count += AddToNextTrains(nextTrains, game.Trains_2, count, max);
			count += AddToNextTrains(nextTrains, game.Trains_3, count, max);
			count += AddToNextTrains(nextTrains, game.Trains_4, count, max);
			count += AddToNextTrains(nextTrains, game.Trains_5, count, max);
			count += AddToNextTrains(nextTrains, game.Trains_6, count, max);
			if (game.MaxTrain8Train)
				count += AddToNextTrains(nextTrains, game.Trains_8, count, max);
			else
				count += AddToNextTrains(nextTrains, game.Trains_D, count, max);

			// TODO: include open market trains

			// TODO: include trains from other companies of this player

			return nextTrains;
		}

		// this might eventually move to train-purchase AI
		static private int AddToNextTrains(TrainCollection nextTrains, TrainCollection fromTrains, int currentCount, int maxCount)
		{
			int added = 0;
			for (int i=0; (i < maxCount - currentCount) && (fromTrains.Count > i); i++)
			{
				nextTrains.Add(fromTrains[i]);
				added++;
			}
			return added;
		}

		static private int CountTrainSize(TrainCollection trains, int stops)
		{
			int count = 0;
			foreach (Train train in trains)
			{
				if (train.Stops == stops)
					count++;
			}
			return count;
		}

		/// <summary>
		/// Return the tile lay to accomodate next turn's expected trainage.
		/// 
		/// Note we swap in the expected trainage in place of current trains
		/// </summary>
		internal override List<AiTileLay> ComputeTileLays(Stopwatch watch, TimeSpan timeLimit, Gameboard gameboard, PublicCompany company, List<AiTileLay> tileLays, Dictionary<string, GameboardHex> runToHexes, Dictionary<string, GameboardHex> tileLayHexes, AiMoveConfig config)
		{
			TrainCollection trains = _nextTurnTrains;
			if (trains == null)
				trains = company.Trains;

			List<string> tooShortEndpoints = new List<string>(); // just pass in an empty one for now

			return ComputeTileLays_Internal(watch, timeLimit,
				gameboard, company, trains, tileLays, tooShortEndpoints, _needIsDesperate,
				runToHexes, tileLayHexes, config);
		}

		/// <summary>
		/// separate method because used to be shared
		/// </summary>
		static private List<AiTileLay> ComputeTileLays_Internal(Stopwatch watch, TimeSpan timeLimit, Gameboard gameboard,
			PublicCompany company, TrainCollection trains,
			List<AiTileLay> tileLays, List<string> tooShortEndpoints, bool needIsDesperate,
			Dictionary<string, GameboardHex> runToHexes, Dictionary<string, GameboardHex> tileLayHexes, AiMoveConfig config)
		{
			List<AiTileLay> bestTileLays = null;
			int bestValue = -1;

			// If token AI will lay a token, take it into account now
			GameboardHex tokenHex;
			if (ImproveCurrentRoutesTileGoal.AiWillPlayToken(out tokenHex))
				ImproveCurrentRoutesTileGoal.ApplyToken(tokenHex);

			foreach (AiTileLay tileLay in tileLays)
			{
				if (watch.Elapsed.CompareTo(timeLimit) > 0)
					goto done;	// time limit expired, return the best tile lay so far

				gameboard.ApplyTileLayWithUndo(tileLay);	// add tile lay to game board
				tileLay.TransferHexNewRoutes(company, gameboard);

				ComputeTileGoalValue(tileLay, gameboard, company, runToHexes, tooShortEndpoints, needIsDesperate);

				gameboard.UndoTileLay(tileLay);	// restore the game board

				if (tileLay.GoalValue == bestValue)
					bestTileLays.Add(tileLay);
				else if (tileLay.GoalValue > bestValue)
				{
					bestTileLays = new List<AiTileLay>();
					bestTileLays.Add(tileLay);
					bestValue = tileLay.GoalValue;
				}
			}

		done:
			return bestTileLays;
		}

		static private void ComputeTileGoalValue(AiTileLay tileLay, Gameboard gameboard, PublicCompany company,
			Dictionary<string, GameboardHex> runToHexes, List<string> tooShortEndpoints, bool needIsDesperate)
		{
			int baseValue = 0;
			int bestValue = 0;

			if (!runToHexes.Keys.Contains<string>(tileLay.Hex.Location))
			{
				// this tile lay connected to a hex the company couldn't reach before,
				// see if it's a city the company couldn't reach before
				if (tileLay.Hex.TownType != GameboardHex.eTownType.None)
					baseValue += tileLay.Hex.Value / 2;
			}
			else
			{
				// it's an upgrade/downgrade tile
				// if it's a city, value the city upgrade (independent of whether our best route increased)
				if (tileLay.Hex.TownType != GameboardHex.eTownType.None)
					baseValue += 4;//9;// tileLay.Hex.Value / 2;
			}

			foreach (string direction in tileLay.NewRoutes)
			{
				int value = 0;
				bool lookAtNext = false;

				// get the tile we connected to, or if no tile, the next hex in that direction
				string sdestHex = (string)tileLay.Hex.Neighbors[direction];
				GameboardHex destHex = gameboard.GameboardHexes[sdestHex];
				if (destHex != null)
				{
					// the tile lay aims at a hex the company couldn't reach before
					if (!runToHexes.Keys.Contains<string>(destHex.Location))
					{
						lookAtNext = (destHex.Tile == null) ;//&& (destHex.TownType != GameboardHex.eTownType.None);
						value += 1;

						// the tile lay aimed at a city the company couldn't reach before
						if ((destHex.TownType == GameboardHex.eTownType.City) ||
							(destHex.HexType == GameboardHex.eHexType.OffBoard))
						{
							value += 2;
							if (needIsDesperate)
								value += 1;
							lookAtNext = false;
						}
						// the tile lay aimed at a town the company couldn't reach before
						else if ((destHex.TownType == GameboardHex.eTownType.Town) ||
							(destHex.TownType == GameboardHex.eTownType.DblTown))
						{
							value += 1;
							if (needIsDesperate)
								value += 1;
							lookAtNext = false;
						}
//						// the tile lay aimed at a double town the company couldn't reach before
//						else if (destHex.TownType == GameboardHex.eTownType.DblTown)
//						{
//							value += 1;
//							lookAtNext = false;
//						}
					}

					if (lookAtNext)
					{
						// check all directions but the one we came from, choosing the highest value
						int highestValue = 0;
						string highestValueDirectionHex = null;
						foreach (string side in destHex.Neighbors.Keys)
						{
							string saimHex = (string)destHex.Neighbors[side];//.Compass];
							if ((saimHex != null) && (saimHex != tileLay.Hex.Location))
							{
								GameboardHex aimHex = gameboard.GameboardHexes[saimHex];
								// the tile lay aims AT a hex the company couldn't reach before
								if ((aimHex != null))//&& (!runToHexes.Keys.Contains<string>(aimHex.Location)))
								{
									int tempValue = 0;
									string oppositeSide = PublicCompany.InverseDirection (side);
									if (aimHex.Tile != null)
									{
										bool hasTrackToThisHex = false;
										foreach (Side s in aimHex.ExistingRoutes.Keys)
										{
											if (s.GetDirection() == oppositeSide)
											{
												hasTrackToThisHex = true;
												break;
											}
										}
										if (hasTrackToThisHex)
										{
											tempValue += 1;
											if ((aimHex.Cities != null) && (aimHex.Cities.Length > 0))
												tempValue += aimHex.Cities[0].Value / 10;
											else
												tempValue += aimHex.Value / 10;
											if (needIsDesperate)
												tempValue += 1;
										}
									}
#if getting_rid_of
									// the tile lay aims AT a city the company couldn't reach before
									if ((aimHex.TownType == GameboardHex.eTownType.City) ||
										(aimHex.HexType == GameboardHex.eHexType.OffBoard))
									{
										tempValue += 1;
										if (needIsDesperate)
											tempValue += 1;
									}
									// the tile lay aims AT a town the company couldn't reach before
									else if ((aimHex.TownType == GameboardHex.eTownType.Town) ||
										(aimHex.TownType == GameboardHex.eTownType.DblTown))
									{
										tempValue += 0;
										if (needIsDesperate)
											tempValue += 1;
									}
#endif // getting_rid_of
									if (tempValue > highestValue)
									{
										highestValue = tempValue;
										highestValueDirectionHex = saimHex;
									}
								}
							}
						}
						value += highestValue;
					}
				}

				// save the highest value for this hex (think about summing them instead)
				//if (value > bestValue)
				//	bestValue = value;

				// sum all the values for this hex
				bestValue += value;
			}

			// HACK: because algorithm values double town tiles too highly, account for that
			if (tileLay.Tile.TownType == GameboardHex.eTownType.DblTown)
			{
				bestValue /= 3;// 2;
				// and those with no upgrade/downgrade path are especially bad
				if ((tileLay.Tile.ValidUpgrades.Count == 0) && (tileLay.Tile.ValidDowngrades.Count == 0))
					bestValue /= 2;
			}

			tileLay.GoalValue = bestValue + baseValue + ExtendRouteFactor(tileLay, tooShortEndpoints);
		}

		static private int ExtendRouteFactor(AiTileLay tileLay, /*Gameboard gameboard,*/ List<string> tooShortEndpoints)
		{
			// an upgrade to an endpoint improves chances of extending a route
			if (tooShortEndpoints.Contains(tileLay.Hex.Location))
				return 30;

			// a tile lay next to an endpoint *might* improve chances
			// TODO: only new outbound routes from the endpoint hex should get added
			foreach (string loc in tooShortEndpoints)
				if (GameEngine.CalculateHexDistance(loc, tileLay.Hex.Location) == 1)
					return 20;

			// no route-extension value
			return 0;
		}
	}
}
